﻿//---------------------------------------------------------------------
// Copyright (c) 2007 Digerati Technologies, LLC.  All Rights Reserved.
//
// Release under the Microsoft Public License (Ms-PL).
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Management;

namespace DigeratiTech.NDiagnostics
{
    /// <summary>
    /// This <see cref="DiagnosticTask"/> verifies that the specified account is a meber of a specific group/role.
    /// </summary>
    public class RoleTask : DiagnosticTask
    {
        #region Private Attributes...

        private string _domain;
        private string _userName;

        #endregion

        #region Properties...

        /// <summary>
        /// Gets or sets the domain to validate against.  Leave domain empty (or null) to 
        /// validate against the local computer
        /// </summary>
        /// <remarks>Setting Domain to an empty string or null will result in the current machine name being used.</remarks>
        public string Domain
        {
            get
            {
                if (string.IsNullOrEmpty(_domain))
                    return Environment.MachineName;
                else
                    return _domain;
            }
            set
            {
                _domain = value;
            }
        }

        /// <summary>
        /// Gets or sets the SAM account name of the user to verify.
        /// </summary>
        /// <value>The SAM account name of the user to verify.</value>
        /// <remarks>Setting UserName to an empty string or null will result in the current user's logon account being used.</remarks>
        public string UserName
        {
            get
            {
                if (string.IsNullOrEmpty(_userName))
                    return Environment.UserName;
                else
                    return _userName;
            }
            set
            {
                _userName = value;
            }
        }

        /// <summary>
        /// Gets or sets the role/group that <see cref="UserName"/>
        /// should be a member of.
        /// </summary>
        /// <value>The the role/group that <see cref="UserName"/>
        /// should be a member of.</value>
        public string Role { get; set; }

        #endregion

        #region Constructors...

        /// <summary>
        /// Initializes a new instance of the <see cref="RoleTask"/> class.
        /// </summary>
        /// <param name="group">The <see cref="TaskGroup"/> this task belongs to.</param>
        /// <param name="name">The task name.</param>
        /// <param name="domain">The domain to validate against.</param>
        /// <param name="userName">The SAM account name of the user to verify.</param>
        /// <param name="role">Specifies the LDAP role/group that <paramref name="userName"/>
        /// should be a member of.</param>
        public RoleTask(TaskGroup group, string name, string domain, string userName, string role)
        {
            taskGroup = group;
            Name = name;
            Domain = domain;
            UserName = userName;
            Role = role;
            Status = TaskStatus.NotRun;
        }

        #endregion

        #region Methods...

        /// <summary>
        /// Executes the task.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if the task is successful; otherwise, <c>false</c>.
        /// </returns>
        public override bool Execute()
        {
            // Start with the assumption that the user is not a member of the specified role
            Status = TaskStatus.Error;
            Message = string.Format("The user '{0}' is not a member of group '{1}'.", UserName, Role);

            // Get a list of all users for the specified group/role
            List<string> userList = GetUserList();

            // Check for the required membership
            if ((userList != null) && (userList.Count > 0))
            {
                foreach (string user in userList)
                {
                    if (user.Equals(UserName, StringComparison.CurrentCultureIgnoreCase))
                    {
                        Status = TaskStatus.Completed;
                        Message = string.Empty;

                        break;
                    }
                }
            }
            else if (userList == null)
            {
                Message = "The group query could not be successfully executed: " + Message;
            }

            return (Message.Length == 0);
        }

        /// <summary>
        /// Retrieves a list of LDAP groups the specified user belongs to.
        /// </summary>
        /// <returns>An <see cref="ArrayList"/> of LDAP groups that the specified user
        /// is a member of.</returns>
        private List<string> GetUserList()
        {
            List<string> userList = new List<string>();

            if (GetUsers(Domain, Role, userList))
                return userList;
            else
                return null;
        }

        /// <summary>
        /// Builds a list of users that belong to the specified group on the specified domain/machine.
        /// </summary>
        /// <param name="domainName">The name of the domain (or machine) the group resides on.</param>
        /// <param name="roleName">The name of the group to enumerate.</param>
        /// <param name="userList">A list of users belonging to the specified group</param>
        /// <returns><c>true</c> if the group names are successfully retrieved; otherwise, <c>false</c>.</returns>
        /// <remarks>Portions of this code derived from: http://www.geekzone.co.nz/chakkaradeep/3938 </remarks>
        private bool GetUsers(String domainName, String roleName, List<string> userList)
        {
            bool success = true;

            try
            {
                // Perform a WMI query to retrieve the specified group
                SelectQuery sQuery = new SelectQuery("Win32_GroupUser",
                    string.Format("GroupComponent=\"Win32_Group.Domain='{0}',Name='{1}'\"", domainName, roleName));

                ManagementObjectSearcher searcher = new ManagementObjectSearcher(sQuery);

                foreach (ManagementObject mgmtObject in searcher.Get())
                {
                    ManagementPath mgmtPath = new ManagementPath(mgmtObject["PartComponent"].ToString());

                    if (mgmtPath.ClassName == "Win32_UserAccount")
                    {
                        // Add the user account to the list
                        userList.Add(GetValue(mgmtPath.RelativePath, "Name"));
                    }
                    else if (mgmtPath.ClassName == "Win32_Group")
                    {
                        // Recursively locate all users for the current group
                        success = GetUsers(GetValue(mgmtPath.RelativePath, "Win32_Group.Domain"), GetValue(mgmtPath.RelativePath, "Name"), userList);
                    }
                }
            }
            catch (Exception ex)
            {
                // If an exception occurs, set the status and message
                Message = ex.Message;
                Status = TaskStatus.Error;

                success = false;
            }

            return success;
        }

        /// <summary>
        /// Retrieves the value for the specified <paramref name="key"/> from
        /// <paramref name="keyValueCollection"/>.
        /// </summary>
        /// <param name="keyValueCollection">A comma-delimited list of key/value pairs (separated
        /// by an equal sign (=).</param>
        /// <param name="key">The key specifying the value to be retrieved.</param>
        /// <returns>A <see cref="string"/> value containing the value referenced by
        /// <paramref name="key"/>.</returns>
        private string GetValue(string keyValueCollection, string key)
        {
            string value = null;
            string[] keyValuePairs = keyValueCollection.Split(',');

            foreach (string keyValueItem in keyValuePairs)
            {
                string[] keyValuePair = keyValueItem.Split('=');

                if (keyValuePair[0].Equals(key, StringComparison.CurrentCultureIgnoreCase))
                {
                    value = keyValuePair[1].Replace("\"", string.Empty);

                    break;
                }
            }

            return value;
        }

        #endregion
    }
}
